撰寫本系列文章目的在於提升資訊安全之實務能力,
並透過實作體悟到資訊安全領域的重要性,
本系列所有文章之內容皆有一定技術水平,
不得從事非法行為、惡意攻擊等非法活動,
「一切不合法規之行為皆受法律所約束」,
為了避免造成公司、廠商或玩家之間困擾,
所有實作不會拿已上市產品、Online Game 等等來作範例學習,
且部分具有深度、價值之內容,將會提升一定閱讀門檻(不對該技術做分析、解說),
請勿透過本系列文章所學,從事任何非法活動,請不要以身試法!!!
首先開始前要先說一下,
小弟我目前還屬於菜鳥階段,正不斷努力學習中,
若有發現錯誤或不妥之處還請不吝賜教。
歡迎大家多多留言,互相交流交流。
然後這篇文章不是從零開始講,所以有些東西需要某些基本知識才能完全掌握。
研究過或是寫過遊戲外掛的人,都應該會知道這個知名的「Blackbone」Library 吧?
Blackbone 是一個 Windows memory hacking library,
程式碼寫得挺漂亮的,可以直接拿來學習、使用(前提要先看懂,因為只是 library),
這個 Library 有非常多實用的功能,今天只有要講一個小功能:INJECT_DLL
首先,先來說一下從 Kernel mode(R0) 注入 DLL 的好處:
再來,根據我的經驗告訴我一件事,DLL Injection 不是一項 「等待被解決的問題」
What is 不是一項 「等待被解決的問題」 ???
意思是說 DLL Injection 這件事情本身就不是問題,
換句話說就是 DLL Injection 太容易了,手法非常非常多,
就技術面來看,基本上保護得再好都有方法能繞過,然後注入,
所以你/妳還在為 DLL Injection 這件事煩惱嗎?
接著,講一下這個 Project 所採用的三種注入技術:
(今天只講第一種,在寫下去內容太多,而且小弟我時間不多 >,<)
(有時間再來寫一篇)
首先,程式碼位於 Inject.c 的 BBInjectDll() 中,
一開始可以看到:
透過 PsLookupProcessByProcessId 拿 Process(注入目標) 的 EProcess
status = PsLookupProcessByProcessId( (HANDLE)pData->pid, &pProcess );
What is EProcess?
這個 EProcess Structure 長怎樣?要怎麼看?
 
 
 
!process 0 0 查看目前打開的所有 Process
lkd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
PROCESS ffffc182be847040
    SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
    DirBase: 001ab000  ObjectTable: ffff848ddf014040  HandleCount: 2192.
    Image: System
PROCESS ffffc182bff3a5c0
    SessionId: none  Cid: 0144    Peb: 775efe7000  ParentCid: 0004
    DirBase: 011b8000  ObjectTable: ffff848ddf5ab500  HandleCount:  52.
    Image: smss.exe
--- --- --- --- --- ---
--- --- --- --- --- ---
--- --- --- --- --- ---
PROCESS ffffc182be948080
    SessionId: 1  Cid: 0888    Peb: 9a982f9000  ParentCid: 11c0
    DirBase: 10b2f000  ObjectTable: ffff848debb41600  HandleCount: 345.
    Image: windbg.exe
PROCESS ffffc182c08835c0
    SessionId: 1  Cid: 1528    Peb: d2ccd46000  ParentCid: 11c0
    DirBase: 109a1000  ObjectTable: ffff848debc60800  HandleCount: 262.
    Image: notepad.exe
dt _eprocess ffffc182c08835c0
ffffc182c08835c0 就是 notepad.exe 的 _EPROCESS 地址lkd> dt _eprocess ffffc182c08835c0
nt!_EPROCESS
   +0x000 Pcb              : _KPROCESS ? //剛剛說到的 KPROCESS
   +0x2d8 ProcessLock      : _EX_PUSH_LOCK
   +0x2e0 UniqueProcessId  : 0x00000000`00001528 Void
   +0x2e8 ActiveProcessLinks : _LIST_ENTRY [ xxx - xxx ]
   +0x2f8 RundownProtect   : _EX_RUNDOWN_REF
    --- --- --- --- --- ---
    --- --- --- --- --- ---
    --- --- --- --- --- ---
   +0x82c MitigationFlags2 : 0
   +0x82c MitigationFlags2Values : <unnamed-tag>
   +0x830 PartitionObject  : 0xffffc182`be8489f0 Void
   //end
好的,現在講回來程式碼,還記得講到哪裡?... 剛拿到注入目標的 EProcess 而已。
繼續往下看會看到正在判斷 pData->type 是什麼類型,
第一個可以看到是 pData->type == IT_MMap(不是這次要講的)
繼續往下看會看到正在拿 Ntdll.dll 的 Base
pNtdll = BBGetUserModule( pProcess, &ustrNtdll, isWow64 );
這邊有判斷目標是不是 Wow64,什麼?你/妳要問什麼是 Wow64 ?
這個不是本章重點,就略過去囉~
繼續往下看會看到正在拿 LdrLoadDll address(實作方法就不說惹)
LdrLoadDll = BBGetModuleExport( pNtdll, "LdrLoadDll", pProcess, NULL );
繼續往下看會看到厲害的東西:
if (PsIsProtectedProcess( pProcess ))
{
    prot.pid         = pData->pid;
    prot.protection  = Policy_Disable;
    prot.dynamicCode = Policy_Disable;
    prot.signature   = Policy_Disable;
    BBSetProtection( &prot );
}
這段 CODE 的用意就是要把 Process 保護拿掉,就是要把以下的值清空,
(這邊不帶大家看 CODE 了,有興趣的可以自己看、自己找)
在 Win10 16299 的 EProcess 中,
有個位置叫做:
+0x6ca Protection       : _PS_PROTECTION
Protection 裡面有三樣東西:
[+0x000] Level                    : 0x0 [Type: unsigned char]
[+0x000 ( 2: 0)] Type             : 0x0 [Type: unsigned char]
[+0x000 ( 3: 3)] Audit            : 0x0 [Type: unsigned char]
[+0x000 ( 7: 4)] Signer           : 0x0 [Type: unsigned char]
所以這些代表什麼? o(≧∀≦)o
好了,我都提到這邊了,請自行谷歌~(>_<。)\,
這個部分涉及部分技術,而且也不是本章重點就不深入討論囉~
繼續往下看會看到正在判斷 if (pData->type == IT_Thread)
耶!終於到了本章重點,LdrLoadDll + ZwCreateThreadEx
xxx pUserBuf = isWow64 ? xxxFunc( xxx ) : BBGetNativeCode( LdrLoadDll, &ustrPath );
if (pData->type == IT_Thread)
{
    status = BBExecuteInNewThread( 
                                pUserBuf, 
                                NULL, 
                                THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER, 
                                pData->wait, 
                                &threadStatus 
                                );
先來看一下這一行:
xxx pUserBuf = isWow64 ? xxxFunc( xxx ) : BBGetNativeCode( LdrLoadDll, &ustrPath );
為了節省時間與文章長度,
直接假設程式走的是 BBGetNativeCode( LdrLoadDll, &ustrPath )
其中 ustrPath 是被注入的 DLL Full Path
跟進去可以看到有一段 shellcode,
這段 shellcode 的用意可以在註解中找到:
// <summary>
// Build injection code for native x64 process
// Must be running in target process context
// </summary>
UCHAR code[] =
{
    0x48, 0x83, 0xEC, 0x28,
    // sub rsp, 0x28
    
    0x48, 0x31, 0xC9,
    // xor rcx, rcx
    
    0x48, 0x31, 0xD2,
    // xor rdx, rdx
    
    0x49, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0,
    // mov r8, ModuleFileName   offset +12
    
    0x49, 0xB9, 0, 0, 0, 0, 0, 0, 0, 0,
    // mov r9, ModuleHandle     offset +22
    
    0x48, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0,
    // mov rax, LdrLoadDll      offset +32
    
    0xFF, 0xD0,
    // call rax
    
    0x48, 0xBA, 0, 0, 0, 0, 0, 0, 0, 0,
    // mov rdx, COMPLETE_OFFSET offset +44
    
    0xC7, 0x02, 0x7E, 0x1E, 0x37, 0xC0,
    // mov [rdx], CALL_COMPLETE 
    
    0x48, 0xBA, 0, 0, 0, 0, 0, 0, 0, 0,
    // mov rdx, STATUS_OFFSET   offset +60
    
    0x89, 0x02,
    // mov [rdx], eax
    
    0x48, 0x83, 0xC4, 0x28,
    // add rsp, 0x28
    
    0xC3
    // ret
};
繼續往下看會看到正在申請記憶體空間:
status = ZwAllocateVirtualMemory( 
                                ZwCurrentProcess(), 
                                &pBuffer, 
                                0, 
                                &size, 
                                MEM_COMMIT, 
                                PAGE_EXECUTE_READWRITE 
                                );
繼續往下看會看到:
// Copy path
PUNICODE_STRING pUserPath = &pBuffer->path;
pUserPath->Length = 0;
pUserPath->MaximumLength = sizeof(pBuffer->buffer);
pUserPath->Buffer = pBuffer->buffer;
RtlUnicodeStringCopy( pUserPath, pPath );
// Copy code
memcpy( pBuffer, code, sizeof( code ) );
// Fill stubs
*(ULONGLONG*)((PUCHAR)pBuffer + 12) = (ULONGLONG)pUserPath;
*(ULONGLONG*)((PUCHAR)pBuffer + 22) = (ULONGLONG)&pBuffer->module;
*(ULONGLONG*)((PUCHAR)pBuffer + 32) = (ULONGLONG)LdrLoadDll;
*(ULONGLONG*)((PUCHAR)pBuffer + 44) = (ULONGLONG)&pBuffer->complete;
*(ULONGLONG*)((PUCHAR)pBuffer + 60) = (ULONGLONG)&pBuffer->status;
繼續往下看就是在 Init routine 了,最後恢復 Process 保護。
實際上到這邊 DLL Injection 就結束了,
不過再往下看的話會看到一些有趣的東西,
例如:
// Unlink module
if (pData->unlink)
// Erase header
if (pData->erasePE)
這是之後要講到的隱藏 Module 方法,
隱藏的方法非常多,之後會舉幾個有趣的例子。
最後整理了一下大致流程:
編譯與執行:
額外補充:
大家若有發現哪裡寫得不好或錯誤的地方,都留個言討論一下吧 XD
那我們下期見 o( ̄▽ ̄)ブ